/*******************************************************************************
        
        copyright:		Copyright (C) Thomas Dixon 2008. All rights reserved
        				
        license:		BSD style: $(LICENSE)
        		
        author(s): 		Original version by Thomas Dixon
        				Modified by Mike Reinhold and Phil DeMonaco.
        				
*******************************************************************************/
module tango.util.cipher.XTEA;

private import tango.util.cipher.Cipher;

/** Implementation of the XTEA cipher designed by
 David Wheeler and Roger Needham. */
class XTEA: BlockCipher {
	private {
		static const uint ROUNDS = 32, KEY_SIZE = 16, BLOCK_SIZE = 8,
				IV_SIZE = 8, DELTA = 0x9e3779b9u;
		uint[] subkeys, sum0, sum1;
		static const uint[] KEY_SIZES = [16]; 
	}

	this(){
	}
	
	this(XTEA other){
		this.subkeys = other.subkeys.dup;
		this.sum0 = other.sum0.dup;
		this.sum1 = other.sum1.dup;
		this._initialized = other._initialized;
		this._encrypt = other._encrypt;
	}
	
	XTEA dup() {
		return new XTEA(this);
	}
	
	/***************************************************************************
	
			reset
									
			Remarks:
			unused

	***************************************************************************/
	final override void reset() {
	}

	/***************************************************************************
			
			name
			
			Returns:
			the string name of the cipher algorithm implemented by this Cipher.
					
	***************************************************************************/
	final override string name() {
		return "XTEA";
	}

	/***************************************************************************
	
			blockSize
			
			Returns:
			size in bytes per block encrypted by this cipher.
	
	***************************************************************************/
	final override uint blockSize() {
		return BLOCK_SIZE;
	}
	
	/***************************************************************************
		
			ivSize
			
			Returns:
			size of the initialization vector used by this cipher.
	
	***************************************************************************/
	uint ivSize(){
		return IV_SIZE;
	}
	
	/***************************************************************************
	
			validKeySizes
						
			Returns:
			a dynamic array containing the key sizes which are valid 
			for this cipher.
	
	***************************************************************************/
	uint[] validKeySizes(){
		return KEY_SIZES;
	}


	/***************************************************************************
	
			init
			
			Params:
			encrypt =	specifies the cipher mode
							true -> encrypt
							false -> decrypt
			keyParams =	SymmetricKey object containing the key material for
						use by the cipher
			
	***************************************************************************/	
	final void init(bool encrypt, SymmetricKey keyParams) {
		_encrypt = encrypt;

		if(keyParams.key.length != KEY_SIZE)
			invalid(name() ~ ": Invalid key length (requires 16 bytes)");

		subkeys = new uint[4];
		sum0 = new uint[32];
		sum1 = new uint[32];

		int i, j;
		for(i = j = 0; i < 4; i++ , j += int.sizeof)
			subkeys[i] = ByteConverter.BigEndian.to!(uint)(
					keyParams.key[j .. j + int.sizeof]);

		// Precompute the values of sum + k[] to speed up encryption
		for(i = j = 0; i < ROUNDS; i++) {
			sum0[i] = (j + subkeys[j & 3]);
			j += DELTA;
			sum1[i] = (j + subkeys[j >> 11 & 3]);
		}

		_initialized = true;
	}

	/***************************************************************************
	
			update
			
			Params:
			input_ =	array containing a single data block
			output_ =	output for the resulting data
						
			Remarks:
			updates the state of the given input block based on the 
			mode specified at the call of init(). The resulting plaintext
			or ciphertext is stored in output_. If either input_ or output_
			are smaller than BLOCK_SIZE an exception will be thrown.

	***************************************************************************/
	final override uint update(void[] input_, void[] output_) {
		if(!_initialized)
			invalid(name() ~ ": Cipher not initialized");

		ubyte[] input = cast(ubyte[]) input_, output = cast(ubyte[]) output_;

		if(input.length < BLOCK_SIZE)
			invalid(name() ~ ": Input buffer too short");

		if(output.length < BLOCK_SIZE)
			invalid(name() ~ ": Output buffer too short");

		uint v0 = ByteConverter.BigEndian.to!(uint)(input[0 .. 4]),
				v1 = ByteConverter.BigEndian.to!(uint)(input[4 .. 8]);

		if(_encrypt) {
			for(int i = 0; i < ROUNDS; i++) {
				v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ sum0[i];
				v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ sum1[i];
			}
		} else {
			for(int i = ROUNDS - 1; i >= 0; i--) {
				v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ sum1[i];
				v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ sum0[i];
			}
		}

		output[0 .. 4] = ByteConverter.BigEndian.from!(uint)(v0);
		output[4 .. 8] = ByteConverter.BigEndian.from!(uint)(v1);

		return BLOCK_SIZE;
	}

	/** Some XTEA test vectors. */
	debug(UnitTest) {
		unittest {
			static string[] test_keys = ["00000000000000000000000000000000",
					"00000000000000000000000000000000",
					"0123456712345678234567893456789a",
					"0123456712345678234567893456789a",
					"00000000000000000000000000000001",
					"01010101010101010101010101010101",
					"0123456789abcdef0123456789abcdef",
					"0123456789abcdef0123456789abcdef",
					"00000000000000000000000000000000",
					"00000000000000000000000000000000"];

			static string[] test_plaintexts = ["0000000000000000",
					"0102030405060708", "0000000000000000", "0102030405060708",
					"0000000000000001", "0101010101010101", "0123456789abcdef",
					"0000000000000000", "0123456789abcdef", "4141414141414141"];

			static string[] test_ciphertexts = ["dee9d4d8f7131ed9",
					"065c1b8975c6a816", "1ff9a0261ac64264", "8c67155b2ef91ead",
					"9f25fa5b0f86b758", "c2eca7cec9b7f992", "27e795e076b2b537",
					"5c8eddc60a95b3e1", "7e66c71c88897221", "ed23375a821a8c2d"];

			XTEA t = new XTEA();
			foreach(uint i, string test_key; test_keys) {
				ubyte[] buffer = new ubyte[t.blockSize];
				string result;
				SymmetricKey key = new SymmetricKey(ByteConverter.hexDecode(
						test_key));

				// Encryption
				t.init(true, key);
				t.update(ByteConverter.hexDecode(test_plaintexts[i]), buffer);
				result = ByteConverter.hexEncode(buffer);
				assert(result == test_ciphertexts[i], t.name ~ ": (" ~ result ~ ") != (" ~ test_ciphertexts[i] ~ ")");

				// Decryption
				t.init(false, key);
				t.update(ByteConverter.hexDecode(test_ciphertexts[i]), buffer);
				result = ByteConverter.hexEncode(buffer);
				assert(result == test_plaintexts[i], t.name ~ ": (" ~ result ~ ") != (" ~ test_plaintexts[i] ~ ")");
			}
		}
	}
}
